Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Node / ReticulumProjects / MeshChatX.git / files / scripts / rpi / install_meshchatx.sh

Displaying Raw • Download


scripts/rpi/install_meshchatx.sh v4.5.0-rc.2 (1394a44a) Text, 14.32 KB

T8b949e#!/usr/bin/env bash
Tffa657set -euo pipefail

Te6edf3DEFAULT_WHEEL_URLTff7b72=Ta5d6ff"https://git.quad4.io/RNS-Things/MeshChatX/releases/download/v4.4.0/reticulum_meshchatx-4.4.0-py3-none-any.whl"

Te6edf3RUN_USERTff7b72=Ta5d6ff"Tffd700${Te6edf3SUDO_USERTff7b72:-Te6edf3$USERTffd700}Ta5d6ff"
Te6edf3RUN_GROUPTff7b72=Ta5d6ff"Tff7b72$(id -gn Ta5d6ff"Te6edf3$RUN_USERTa5d6ff"Tff7b72)Ta5d6ff"
Te6edf3USER_HOMETff7b72=Ta5d6ff"Tff7b72$(Tffa657eval Tffa657echo Ta5d6ff"Ta5d6ff~Tffd700${Te6edf3RUN_USERTffd700}Ta5d6ff"Tff7b72)Ta5d6ff"

Tff7b72if Tff7b72[Tff7b72[ -t T79c0ff1 Tff7b72]Tff7b72] Tff7b72&& Tffa657command -v tput >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Te6edf3C_RESETTff7b72=Ta5d6ff"Tff7b72$(tput sgr0Tff7b72)Ta5d6ff"
Te6edf3C_BOLDTff7b72=Ta5d6ff"Tff7b72$(tput boldTff7b72)Ta5d6ff"
Te6edf3C_REDTff7b72=Ta5d6ff"Tff7b72$(tput setaf T79c0ff1Tff7b72)Ta5d6ff"
Te6edf3C_GREENTff7b72=Ta5d6ff"Tff7b72$(tput setaf T79c0ff2Tff7b72)Ta5d6ff"
Te6edf3C_YELLOWTff7b72=Ta5d6ff"Tff7b72$(tput setaf T79c0ff3Tff7b72)Ta5d6ff"
Te6edf3C_BLUETff7b72=Ta5d6ff"Tff7b72$(tput setaf T79c0ff4Tff7b72)Ta5d6ff"
Tff7b72else
Te6edf3C_RESETTff7b72=Ta5d6ff""
Te6edf3C_BOLDTff7b72=Ta5d6ff""
Te6edf3C_REDTff7b72=Ta5d6ff""
Te6edf3C_GREENTff7b72=Ta5d6ff""
Te6edf3C_YELLOWTff7b72=Ta5d6ff""
Te6edf3C_BLUETff7b72=Ta5d6ff""
Tff7b72fi

noteTff7b72(Tff7b72) Tff7b72{
Tffa657echo Ta5d6ff"Tffd700${Te6edf3C_BLUETffd700}Tffd700${Te6edf3C_BOLDTffd700}Ta5d6ff==>Tffd700${Te6edf3C_RESETTffd700}Ta5d6ff Te6edf3$*Ta5d6ff"
Tff7b72}

warnTff7b72(Tff7b72) Tff7b72{
Tffa657echo Ta5d6ff"Tffd700${Te6edf3C_YELLOWTffd700}Tffd700${Te6edf3C_BOLDTffd700}Ta5d6ffWARN:Tffd700${Te6edf3C_RESETTffd700}Ta5d6ff Te6edf3$*Ta5d6ff"
Tff7b72}

errTff7b72(Tff7b72) Tff7b72{
Tffa657echo Ta5d6ff"Tffd700${Te6edf3C_REDTffd700}Tffd700${Te6edf3C_BOLDTffd700}Ta5d6ffERROR:Tffd700${Te6edf3C_RESETTffd700}Ta5d6ff Te6edf3$*Ta5d6ff" >Tb4b4b4&T79c0ff2
Tff7b72}

okTff7b72(Tff7b72) Tff7b72{
Tffa657echo Ta5d6ff"Tffd700${Te6edf3C_GREENTffd700}Tffd700${Te6edf3C_BOLDTffd700}Ta5d6ffOK:Tffd700${Te6edf3C_RESETTffd700}Ta5d6ff Te6edf3$*Ta5d6ff"
Tff7b72}

run_as_userTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3cmdTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72&& Ta5d6ff"Te6edf3$RUN_USERTa5d6ff" !Tff7b72= Ta5d6ff"root" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
sudo -u Ta5d6ff"Te6edf3$RUN_USERTa5d6ff" -H bash -lc Ta5d6ff"Te6edf3$cmdTa5d6ff"
Tff7b72else
bash -lc Ta5d6ff"Te6edf3$cmdTa5d6ff"
Tff7b72fi
Tff7b72}

prompt_defaultTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3promptTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3defaultTff7b72=Ta5d6ff"Te6edf3$2Ta5d6ff"
Tffa657local Te6edf3valueTff7b72=Ta5d6ff""
Tffa657read -r -p Ta5d6ff"Te6edf3$promptTa5d6ff [Te6edf3$defaultTa5d6ff]: Ta5d6ff" value
Tff7b72if Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$valueTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3valueTff7b72=Ta5d6ff"Te6edf3$defaultTa5d6ff"
Tff7b72fi
Tffa657echo Ta5d6ff"Te6edf3$valueTa5d6ff"
Tff7b72}

prompt_yes_noTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3promptTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3defaultTff7b72=Ta5d6ff"Tffd700${Te6edf32Tff7b72:-Te6edf3yTffd700}Ta5d6ff"
Tffa657local Te6edf3answerTff7b72=Ta5d6ff""
Tffa657local Te6edf3hintTff7b72=Ta5d6ff"Y/n"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$defaultTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"n" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3hintTff7b72=Ta5d6ff"y/N"
Tff7b72fi

Tff7b72while trueTb4b4b4; Tff7b72do
Tffa657read -r -p Ta5d6ff"Te6edf3$promptTa5d6ff (Te6edf3$hintTa5d6ff): Ta5d6ff" answer
Tff7b72if Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$answerTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3answerTff7b72=Ta5d6ff"Te6edf3$defaultTa5d6ff"
Tff7b72fi
Tff7b72case Ta5d6ff"Tffd700${Te6edf3answerTb4b4b4,,Tffd700}Ta5d6ff" Tff7b72in
yTb4b4b4|yesTff7b72) Tff7b72return T79c0ff0 Tb4b4b4;Tb4b4b4;
nTb4b4b4|noTff7b72) Tff7b72return T79c0ff1 Tb4b4b4;Tb4b4b4;
*Tff7b72)
Tffa657echo Ta5d6ff"Please answer y or n."
Tb4b4b4;Tb4b4b4;
Tff7b72esac
Tff7b72done
Tff7b72}

pick_package_managerTff7b72(Tff7b72) Tff7b72{
Tff7b72if Tffa657command -v apt-get >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff"apt"
Tff7b72return
Tff7b72fi
Tff7b72if Tffa657command -v dnf >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff"dnf"
Tff7b72return
Tff7b72fi
Tff7b72if Tffa657command -v pacman >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff"pacman"
Tff7b72return
Tff7b72fi
Tffa657echo Ta5d6ff"none"
Tff7b72}

install_package_if_possibleTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3packageTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local mgr
Te6edf3mgrTff7b72=Ta5d6ff"Tff7b72$(pick_package_managerTff7b72)Ta5d6ff"
Tff7b72case Ta5d6ff"Te6edf3$mgrTa5d6ff" Tff7b72in
aptTff7b72)
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
apt-get update Tff7b72&& apt-get install -y Ta5d6ff"Te6edf3$packageTa5d6ff"
Tff7b72else
sudo apt-get update Tff7b72&& sudo apt-get install -y Ta5d6ff"Te6edf3$packageTa5d6ff"
Tff7b72fi
Tb4b4b4;Tb4b4b4;
dnfTff7b72)
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
dnf install -y Ta5d6ff"Te6edf3$packageTa5d6ff"
Tff7b72else
sudo dnf install -y Ta5d6ff"Te6edf3$packageTa5d6ff"
Tff7b72fi
Tb4b4b4;Tb4b4b4;
pacmanTff7b72)
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
pacman -Sy --noconfirm Ta5d6ff"Te6edf3$packageTa5d6ff"
Tff7b72else
sudo pacman -Sy --noconfirm Ta5d6ff"Te6edf3$packageTa5d6ff"
Tff7b72fi
Tb4b4b4;Tb4b4b4;
noneTff7b72)
warn Ta5d6ff"Ta5d6ffNo supported package manager found (apt/dnf/pacman). Skipping install for Te6edf3$packageTa5d6ff.Ta5d6ff"
Tb4b4b4;Tb4b4b4;
Tff7b72esac
Tff7b72}

check_port_availableTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3hostTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3portTff7b72=Ta5d6ff"Te6edf3$2Ta5d6ff"
python3 - Ta5d6ff"Te6edf3$hostTa5d6ff" Ta5d6ff"Te6edf3$portTa5d6ff" Ta5d6ff<<'PY'
import socket
import sys

host = sys.argv[1]
port = int(sys.argv[2])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((host, port))
except OSError:
sys.exit(1)
finally:
s.close()
sys.exit(0)
PY
Tff7b72}

detect_archTff7b72(Tff7b72) Tff7b72{
python3 - Ta5d6ff<<'PY'
import platform
print(platform.machine().strip().lower())
PY
Tff7b72}

is_supported_rpi_archTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3archTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tff7b72case Ta5d6ff"Te6edf3$archTa5d6ff" Tff7b72in
armv6lTb4b4b4|armv7lTb4b4b4|aarch64Tb4b4b4|arm64Tff7b72)
Tff7b72return T79c0ff0
Tb4b4b4;Tb4b4b4;
*Tff7b72)
Tff7b72return T79c0ff1
Tb4b4b4;Tb4b4b4;
Tff7b72esac
Tff7b72}

write_system_serviceTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3exec_cmdTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3workdirTff7b72=Ta5d6ff"Te6edf3$2Ta5d6ff"
Tffa657local Te6edf3path_valueTff7b72=Ta5d6ff"Te6edf3$3Ta5d6ff"
Tffa657local Te6edf3svcTff7b72=Ta5d6ff"/etc/systemd/system/meshchatx.service"

Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3SUDOTff7b72=Ta5d6ff""
Tff7b72else
Te6edf3SUDOTff7b72=Ta5d6ff"sudo"
Tff7b72fi

Te6edf3$SUDO tee Ta5d6ff"Te6edf3$svcTa5d6ff" >/dev/null Ta5d6ff<<EOF
[Unit]
Description=MeshChatX Headless (system service)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=${RUN_USER}
Group=${RUN_GROUP}
WorkingDirectory=${workdir}
Environment="PATH=${path_value}"
ExecStart=${exec_cmd}
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
Tff7b72}

write_user_serviceTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3exec_cmdTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3workdirTff7b72=Ta5d6ff"Te6edf3$2Ta5d6ff"
Tffa657local Te6edf3path_valueTff7b72=Ta5d6ff"Te6edf3$3Ta5d6ff"
Tffa657local Te6edf3svc_pathTff7b72=Ta5d6ff"Tffd700${Te6edf3USER_HOMETffd700}Ta5d6ff/.config/systemd/user/meshchatx.serviceTa5d6ff"

run_as_user Ta5d6ff"Ta5d6ffmkdir -p 'Tffd700${Te6edf3USER_HOMETffd700}Ta5d6ff/.config/systemd/user'Ta5d6ff"
run_as_user Ta5d6ff"Ta5d6ffcat > 'Tffd700${Te6edf3svc_pathTffd700}Ta5d6ff' <<'EOF'
[Unit]
Description=MeshChatX Headless (user service)
After=network-online.target

[Service]
Type=simple
WorkingDirectory=Tffd700${Te6edf3workdirTffd700}
Ta5d6ffEnvironment=\"PATH=Tffd700${Te6edf3path_valueTffd700}Ta5d6ff\"
ExecStart=Tffd700${Te6edf3exec_cmdTffd700}
Ta5d6ffRestart=always
RestartSec=3

[Install]
WantedBy=default.target
EOFTa5d6ff"
Tff7b72}

handle_service_start_failureTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3modeTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3reasonTff7b72=Ta5d6ff"Te6edf3$2Ta5d6ff"

err Ta5d6ff"Te6edf3$reasonTa5d6ff"
warn Ta5d6ff"Service startup failed. Recent logs:"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$modeTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"system" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
sudo journalctl -u meshchatx.service -n T79c0ff200 --no-pager Tff7b72|| Tffa657true
sudo systemctl stop meshchatx.service Tff7b72|| Tffa657true
sudo systemctl reset-failed meshchatx.service Tff7b72|| Tffa657true
Tff7b72else
run_as_user Ta5d6ff"journalctl --user -u meshchatx.service -n 200 --no-pager" Tff7b72|| Tffa657true
run_as_user Ta5d6ff"systemctl --user stop meshchatx.service || true"
run_as_user Ta5d6ff"systemctl --user reset-failed meshchatx.service || true"
Tff7b72fi
err Ta5d6ff"Service was stopped/reset. Fix config and run installer again."
Tffa657exit T79c0ff1
Tff7b72}

verify_service_startedTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3modeTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3probe_hostTff7b72=Ta5d6ff"Te6edf3$2Ta5d6ff"
Tffa657local Te6edf3probe_portTff7b72=Ta5d6ff"Te6edf3$3Ta5d6ff"
Tffa657local Te6edf3https_enabledTff7b72=Ta5d6ff"Te6edf3$4Ta5d6ff"
Tffa657local Te6edf3triesTff7b72=T79c0ff40
Tffa657local Te6edf3log_cmdTff7b72=Ta5d6ff""
Tffa657local Te6edf3stop_cmdTff7b72=Ta5d6ff""
Tffa657local Te6edf3schemeTff7b72=Ta5d6ff"https"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$https_enabledTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"no" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3schemeTff7b72=Ta5d6ff"http"
Tff7b72fi

Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$modeTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"system" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3log_cmdTff7b72=Ta5d6ff"sudo journalctl -u meshchatx.service -n 200 --no-pager"
Te6edf3stop_cmdTff7b72=Ta5d6ff"sudo systemctl stop meshchatx.service; sudo systemctl reset-failed meshchatx.service"
Tff7b72else
Te6edf3log_cmdTff7b72=Ta5d6ff"journalctl --user -u meshchatx.service -n 200 --no-pager"
Te6edf3stop_cmdTff7b72=Ta5d6ff"systemctl --user stop meshchatx.service || true; systemctl --user reset-failed meshchatx.service || true"
Tff7b72fi

note Ta5d6ff"Ta5d6ffVerifying service startup via Tffd700${Te6edf3schemeTffd700}Ta5d6ff://Tffd700${Te6edf3probe_hostTffd700}Ta5d6ff:Tffd700${Te6edf3probe_portTffd700}Ta5d6ff/api/v1/status ...Ta5d6ff"
Tff7b72for _ Tff7b72in Tff7b72$(seq T79c0ff1 Ta5d6ff"Te6edf3$triesTa5d6ff"Tff7b72)Tb4b4b4; Tff7b72do
Tff7b72if python3 - Ta5d6ff"Te6edf3$schemeTa5d6ff" Ta5d6ff"Te6edf3$probe_hostTa5d6ff" Ta5d6ff"Te6edf3$probe_portTa5d6ff" Ta5d6ff<<'PY'
import json
import ssl
import sys
import urllib.request

scheme, host, port = sys.argv[1], sys.argv[2], int(sys.argv[3])
url = f"{scheme}://{host}:{port}/api/v1/status"

ctx = ssl._create_unverified_context() if scheme == "https" else None
req = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(req, timeout=2, context=ctx) as resp:
if resp.status != 200:
raise RuntimeError(f"unexpected status {resp.status}")
data = json.loads(resp.read().decode("utf-8"))
if data.get("status") != "ok":
raise RuntimeError("status payload is not ok")
except Exception:
sys.exit(1)
sys.exit(0)
PY
Tff7b72then
ok Ta5d6ff"Service started successfully (status endpoint is healthy)."
Tff7b72return T79c0ff0
Tff7b72fi
sleep T79c0ff1
Tff7b72done

err Ta5d6ff"Service did not pass status endpoint health check."
warn Ta5d6ff"Recent logs:"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$modeTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"system" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
sudo journalctl -u meshchatx.service -n T79c0ff200 --no-pager Tff7b72|| Tffa657true
Tffa657eval Ta5d6ff"Te6edf3$stop_cmdTa5d6ff"
Tff7b72else
run_as_user Ta5d6ff"Te6edf3$log_cmdTa5d6ff" Tff7b72|| Tffa657true
run_as_user Ta5d6ff"Te6edf3$stop_cmdTa5d6ff"
Tff7b72fi
err Ta5d6ff"Service was stopped to prevent restart loops."
Tff7b72return T79c0ff1
Tff7b72}

mainTff7b72(Tff7b72) Tff7b72{
note Ta5d6ff"MeshChatX Raspberry Pi Interactive Installer"
Tffa657echo Ta5d6ff"Ta5d6ffDetected user: Tffd700${Te6edf3RUN_USERTffd700}Ta5d6ff (group: Tffd700${Te6edf3RUN_GROUPTffd700}Ta5d6ff)Ta5d6ff"
Tffa657local arch
Te6edf3archTff7b72=Ta5d6ff"Tff7b72$(detect_archTff7b72)Ta5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ffDetected architecture: Tffd700${Te6edf3archTffd700}Ta5d6ff"
Tff7b72if is_supported_rpi_arch Ta5d6ff"Te6edf3$archTa5d6ff"Tb4b4b4; Tff7b72then
ok Ta5d6ff"Ta5d6ffDetected Raspberry Pi ARM architecture (Tffd700${Te6edf3archTffd700}Ta5d6ff).Ta5d6ff"
Tff7b72else
warn Ta5d6ff"Ta5d6ffDetected non-RPi arch (Tffd700${Te6edf3archTffd700}Ta5d6ff). Script can still run, but this guide targets Raspberry Pi ARM.Ta5d6ff"
Tff7b72fi
note Ta5d6ff"The wheel is py3-none-any, so it is architecture-independent (32-bit and 64-bit ARM are supported)."
Tffa657echo

Tff7b72if prompt_yes_no Ta5d6ff"Do you want to install espeak-ng?" Ta5d6ff"y"Tb4b4b4; Tff7b72then
note Ta5d6ff"Installing espeak-ng (best effort)..."
Tff7b72if ! install_package_if_possible Ta5d6ff"espeak-ng"Tb4b4b4; Tff7b72then
warn Ta5d6ff"Could not install espeak-ng automatically; continuing."
Tff7b72fi
Tff7b72else
note Ta5d6ff"Skipping espeak-ng installation."
Tff7b72fi

Tffa657local Te6edf3method_choiceTff7b72=Ta5d6ff""
Tff7b72while Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$method_choiceTa5d6ff" !Tff7b72= Ta5d6ff"1" Tff7b72&& Ta5d6ff"Te6edf3$method_choiceTa5d6ff" !Tff7b72= Ta5d6ff"2" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72do
Tffa657echo
Tffa657echo Ta5d6ff"Choose installation method:"
Tffa657echo Ta5d6ff" 1) pipx (recommended)"
Tffa657echo Ta5d6ff" 2) venv + pip"
Tffa657read -r -p Ta5d6ff"Selection [1/2]: " method_choice
Tff7b72if Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$method_choiceTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3method_choiceTff7b72=Ta5d6ff"1"
Tff7b72fi
Tff7b72done

Tffa657local wheel_url
Te6edf3wheel_urlTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Wheel URL" Ta5d6ff"Te6edf3$DEFAULT_WHEEL_URLTa5d6ff"Tff7b72)Ta5d6ff"

Tffa657local install_root
Te6edf3install_rootTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Install root directory" Ta5d6ff"Tffd700${Te6edf3USER_HOMETffd700}Ta5d6ff/meshchatxTa5d6ff"Tff7b72)Ta5d6ff"
Tffa657local storage_dir
Te6edf3storage_dirTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Storage directory" Ta5d6ff"Tffd700${Te6edf3install_rootTffd700}Ta5d6ff/storageTa5d6ff"Tff7b72)Ta5d6ff"
Tffa657local rns_dir
Te6edf3rns_dirTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Reticulum config directory" Ta5d6ff"Tffd700${Te6edf3USER_HOMETffd700}Ta5d6ff/.reticulumTa5d6ff"Tff7b72)Ta5d6ff"
Tffa657local bind_host
Te6edf3bind_hostTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Bind IP/host" Ta5d6ff"0.0.0.0"Tff7b72)Ta5d6ff"
Tffa657local bind_port
Te6edf3bind_portTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Bind port" Ta5d6ff"8000"Tff7b72)Ta5d6ff"

Tff7b72if check_port_available Ta5d6ff"Te6edf3$bind_hostTa5d6ff" Ta5d6ff"Te6edf3$bind_portTa5d6ff"Tb4b4b4; Tff7b72then
ok Ta5d6ff"Ta5d6ffPort Tffd700${Te6edf3bind_portTffd700}Ta5d6ff is available on Tffd700${Te6edf3bind_hostTffd700}Ta5d6ff.Ta5d6ff"
Tff7b72else
warn Ta5d6ff"Ta5d6ffPort Tffd700${Te6edf3bind_portTffd700}Ta5d6ff appears to be in use on Tffd700${Te6edf3bind_hostTffd700}Ta5d6ff.Ta5d6ff"
Tff7b72if ! prompt_yes_no Ta5d6ff"Continue anyway?" Ta5d6ff"n"Tb4b4b4; Tff7b72then
err Ta5d6ff"Aborted."
Tffa657exit T79c0ff1
Tff7b72fi
Tff7b72fi

Tffa657local Te6edf3https_enabledTff7b72=Ta5d6ff"yes"
Tff7b72if ! prompt_yes_no Ta5d6ff"Enable HTTPS?" Ta5d6ff"y"Tb4b4b4; Tff7b72then
Te6edf3https_enabledTff7b72=Ta5d6ff"no"
Tff7b72fi

Tffa657local Te6edf3service_modeTff7b72=Ta5d6ff"none"
Tff7b72if prompt_yes_no Ta5d6ff"Do you want to configure a systemd service?" Ta5d6ff"y"Tb4b4b4; Tff7b72then
Te6edf3service_modeTff7b72=Ta5d6ff""
Tff7b72while Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$service_modeTa5d6ff" !Tff7b72= Ta5d6ff"system" Tff7b72&& Ta5d6ff"Te6edf3$service_modeTa5d6ff" !Tff7b72= Ta5d6ff"user" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72do
Te6edf3service_modeTff7b72=Ta5d6ff"Tff7b72$(prompt_default Ta5d6ff"Service mode (system/user)" Ta5d6ff"system"Tff7b72)Ta5d6ff"
Tff7b72done
Tff7b72fi

Tffa657local Te6edf3no_https_flagTff7b72=Ta5d6ff""
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$https_enabledTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"no" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3no_https_flagTff7b72=Ta5d6ff" --no-https"
Tff7b72fi

Tffa657local Te6edf3probe_hostTff7b72=Ta5d6ff"Te6edf3$bind_hostTa5d6ff"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$bind_hostTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"0.0.0.0" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3probe_hostTff7b72=Ta5d6ff"127.0.0.1"
Tff7b72elif Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$bind_hostTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"::" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3probe_hostTff7b72=Ta5d6ff"::1"
Tff7b72fi

note Ta5d6ff"Preparing directories..."
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
install -d -m T79c0ff755 -o Ta5d6ff"Te6edf3$RUN_USERTa5d6ff" -g Ta5d6ff"Te6edf3$RUN_GROUPTa5d6ff" Ta5d6ff"Te6edf3$install_rootTa5d6ff" Ta5d6ff"Te6edf3$storage_dirTa5d6ff" Ta5d6ff"Te6edf3$rns_dirTa5d6ff"
Tff7b72else
mkdir -p Ta5d6ff"Te6edf3$install_rootTa5d6ff" Ta5d6ff"Te6edf3$storage_dirTa5d6ff" Ta5d6ff"Te6edf3$rns_dirTa5d6ff"
Tff7b72if Tffa657command -v sudo >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
sudo install -d -m T79c0ff755 -o Ta5d6ff"Te6edf3$RUN_USERTa5d6ff" -g Ta5d6ff"Te6edf3$RUN_GROUPTa5d6ff" Ta5d6ff"Te6edf3$install_rootTa5d6ff" Ta5d6ff"Te6edf3$storage_dirTa5d6ff" Ta5d6ff"Te6edf3$rns_dirTa5d6ff" >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1 Tff7b72|| Tffa657true
Tff7b72fi
Tff7b72fi

Tffa657local Te6edf3bin_pathTff7b72=Ta5d6ff""
Tffa657local Te6edf3venv_pathTff7b72=Ta5d6ff"Tffd700${Te6edf3install_rootTffd700}Ta5d6ff/.venvTa5d6ff"

Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$method_choiceTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"1" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if ! Tffa657command -v pipx >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
err Ta5d6ff"pipx not found. Install pipx first or choose venv method."
Tffa657exit T79c0ff1
Tff7b72fi
note Ta5d6ff"Installing MeshChatX via pipx..."
run_as_user Ta5d6ff"pipx ensurepath >/dev/null 2>&1 || true"
run_as_user Ta5d6ff"Ta5d6ffpipx install --force 'Tffd700${Te6edf3wheel_urlTffd700}Ta5d6ff'Ta5d6ff"
run_as_user Ta5d6ff"pipx inject reticulum-meshchatx packaging >/dev/null 2>&1 || true"
Te6edf3bin_pathTff7b72=Ta5d6ff"Tffd700${Te6edf3USER_HOMETffd700}Ta5d6ff/.local/bin/meshchatxTa5d6ff"
Tff7b72else
note Ta5d6ff"Installing MeshChatX via venv + pip..."
run_as_user Ta5d6ff"Ta5d6ffpython3 -m venv 'Tffd700${Te6edf3venv_pathTffd700}Ta5d6ff'Ta5d6ff"
run_as_user Ta5d6ff"Ta5d6ff'Tffd700${Te6edf3venv_pathTffd700}Ta5d6ff/bin/python' -m pip install --upgrade pipTa5d6ff"
run_as_user Ta5d6ff"Ta5d6ff'Tffd700${Te6edf3venv_pathTffd700}Ta5d6ff/bin/python' -m pip install 'Tffd700${Te6edf3wheel_urlTffd700}Ta5d6ff'Ta5d6ff"
run_as_user Ta5d6ff"Ta5d6ff'Tffd700${Te6edf3venv_pathTffd700}Ta5d6ff/bin/python' -m pip install packagingTa5d6ff"
Te6edf3bin_pathTff7b72=Ta5d6ff"Tffd700${Te6edf3venv_pathTffd700}Ta5d6ff/bin/meshchatxTa5d6ff"
Tff7b72fi

Tff7b72if Tff7b72[Tff7b72[ ! -x Ta5d6ff"Te6edf3$bin_pathTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
err Ta5d6ff"Ta5d6ffmeshchatx binary not found at: Te6edf3$bin_pathTa5d6ff"
Tffa657exit T79c0ff1
Tff7b72fi

Tffa657local Te6edf3exec_cmdTff7b72=Ta5d6ff"Tffd700${Te6edf3bin_pathTffd700}Ta5d6ff --headless --host Tffd700${Te6edf3bind_hostTffd700}Ta5d6ff --port Tffd700${Te6edf3bind_portTffd700}Ta5d6ff --storage-dir Tffd700${Te6edf3storage_dirTffd700}Ta5d6ff --reticulum-config-dir Tffd700${Te6edf3rns_dirTffd700}Tffd700${Te6edf3no_https_flagTffd700}Ta5d6ff"
Tffa657local Te6edf3path_envTff7b72=Ta5d6ff"Tffd700${Te6edf3venv_pathTffd700}Ta5d6ff/bin:Tffd700${Te6edf3USER_HOMETffd700}Ta5d6ff/.local/bin:/usr/bin:/binTa5d6ff"

Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$service_modeTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"none" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
ok Ta5d6ff"Install complete."
Tffa657echo
Tffa657echo Ta5d6ff"Run command:"
Tffa657echo Ta5d6ff"Ta5d6ff Tffd700${Te6edf3exec_cmdTffd700}Ta5d6ff"
Tffa657exit T79c0ff0
Tff7b72fi

Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$service_modeTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"system" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
note Ta5d6ff"Creating system service..."
write_system_service Ta5d6ff"Te6edf3$exec_cmdTa5d6ff" Ta5d6ff"Te6edf3$install_rootTa5d6ff" Ta5d6ff"Te6edf3$path_envTa5d6ff"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$EUIDTa5d6ff" -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if ! systemctl daemon-reloadTb4b4b4; Tff7b72then
handle_service_start_failure Ta5d6ff"system" Ta5d6ff"systemctl daemon-reload failed."
Tff7b72fi
Tff7b72if ! systemctl Tffa657enable --now meshchatx.serviceTb4b4b4; Tff7b72then
handle_service_start_failure Ta5d6ff"system" Ta5d6ff"systemctl enable/start failed."
Tff7b72fi
Tff7b72else
Tff7b72if ! sudo systemctl daemon-reloadTb4b4b4; Tff7b72then
handle_service_start_failure Ta5d6ff"system" Ta5d6ff"sudo systemctl daemon-reload failed."
Tff7b72fi
Tff7b72if ! sudo systemctl Tffa657enable --now meshchatx.serviceTb4b4b4; Tff7b72then
handle_service_start_failure Ta5d6ff"system" Ta5d6ff"sudo systemctl enable/start failed."
Tff7b72fi
Tff7b72fi
Tff7b72if ! verify_service_started Ta5d6ff"system" Ta5d6ff"Te6edf3$probe_hostTa5d6ff" Ta5d6ff"Te6edf3$bind_portTa5d6ff" Ta5d6ff"Te6edf3$https_enabledTa5d6ff"Tb4b4b4; Tff7b72then
Tffa657exit T79c0ff1
Tff7b72fi
ok Ta5d6ff"System service is enabled and running."
Tff7b72else
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$service_modeTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"user" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
note Ta5d6ff"Creating user service..."
write_user_service Ta5d6ff"Te6edf3$exec_cmdTa5d6ff" Ta5d6ff"Te6edf3$install_rootTa5d6ff" Ta5d6ff"Te6edf3$path_envTa5d6ff"
Tff7b72if ! run_as_user Ta5d6ff"systemctl --user daemon-reload"Tb4b4b4; Tff7b72then
handle_service_start_failure Ta5d6ff"user" Ta5d6ff"systemctl --user daemon-reload failed."
Tff7b72fi
Tff7b72if ! run_as_user Ta5d6ff"systemctl --user enable --now meshchatx.service"Tb4b4b4; Tff7b72then
handle_service_start_failure Ta5d6ff"user" Ta5d6ff"systemctl --user enable/start failed."
Tff7b72fi
Tff7b72if ! verify_service_started Ta5d6ff"user" Ta5d6ff"Te6edf3$probe_hostTa5d6ff" Ta5d6ff"Te6edf3$bind_portTa5d6ff" Ta5d6ff"Te6edf3$https_enabledTa5d6ff"Tb4b4b4; Tff7b72then
Tffa657exit T79c0ff1
Tff7b72fi
ok Ta5d6ff"User service is enabled and running."
Tff7b72fi
Tff7b72fi

Tffa657echo
Tffa657echo Ta5d6ff"Web UI:"
Tffa657echo Ta5d6ff"Ta5d6ff https://Tffd700${Te6edf3bind_hostTffd700}Ta5d6ff:Tffd700${Te6edf3bind_portTffd700}Ta5d6ff"
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$https_enabledTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"no" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tffa657echo Ta5d6ff" (HTTP mode enabled)"
Tff7b72fi
Tff7b72}

main Ta5d6ff"Te6edf3$@Ta5d6ff"


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────